#pragma once

#include "gl/glew.h"
#include <vector>
#include <string>
#include "Renderer.h"
using namespace std;

class Model {
public:
	virtual ~Model() {}
	void virtual draw()=0;
};

class Light {

};

class Camera {
	mat4 cTransform;
	mat4 projection;
	mat4 toWorldTransformer; // multiply by this matrix in order to get the current world coordinates...
	vec4 placeInWorld;
	vec3 m_eye;
	vec3 m_at;
	vec3 m_up;
	bool m_is_ortho;
	vec2 center;
	GLfloat m_right;
	GLfloat m_left;
	GLfloat m_top;
	GLfloat m_bottom;
	GLfloat m_zNear;
	GLfloat m_zFar;
public:
	inline bool isOrtho() const { return m_is_ortho; }
	void setTransformation(const mat4& transform); // what the hell is this?!
	void LookAt(const vec3& eye, const vec3& at, const vec3& up );
	void Ortho( const GLfloat left, const GLfloat right,
		const GLfloat bottom, const GLfloat top,
		const GLfloat zNear, const GLfloat zFar );
	void Frustum( const float left, const float right,
		const float bottom, const float top,
		const float zNear, const float zFar );
	//similar to Frustum. So I changed the return value type to void...
	void Perspective( const float fovy, const float aspect,
		const float zNear, const float zFar);
	inline mat4 getCameraTransform() {return cTransform;}
	inline mat4 getProjectionTransform() { return projection; }
	inline mat4 getCorrector() { return toWorldTransformer; }
	inline vec4 getPlace() { return placeInWorld; }
	inline vec3 getUp() { return m_up; }
	inline vec3 getAt() { return m_at; }
	inline vec3 getEye() { return m_eye; }
	void Rotate(const GLfloat & x, const GLfloat & y, const GLfloat & z);
	void Move(const GLfloat & x, const GLfloat & y, const GLfloat & z);
	void UpdateProjection(const GLfloat & ratio);
	void Zoom(GLfloat rate);
	void getProjectionDimensions(GLfloat & width, GLfloat & height, GLfloat & depth);
	void MoveZNear(GLfloat len);
	void MoveZFar(GLfloat len);
	vec3 SuggestMountingPoint() const;
};

class Scene {

	vector<Model*> models;
	vector<Light*> lights; 
	vector<Camera*> cameras;
	Renderer *m_renderer;
	//the following two fields will determine the projection cube.
	int activeCamera;
	int activeModel;
	int activeLight;
	int m_drawCameras;
	bool flagDrawNormals;
public:
	Scene(Renderer *renderer);
	~Scene();
	void NextModel();
	void PreviousModel();
	void loadOBJModel(string fileName,bool centralize, bool shrink, vec3 mp);
	void draw();
	void drawDemo();
	void TranslateActiveModel(const GLfloat & x_dir, const GLfloat  & y_dir, const GLfloat & z_dir);
	void redraw();
	inline void AddCamera(Camera * c) { this->cameras.push_back(c); } 
	void SpinModel(const GLfloat & x_angle, const GLfloat & y_angle, const GLfloat & z_angle);
	void RotateModelAboutX(const GLfloat & angle);
	void RotateModelAboutY(const GLfloat & angle);
	void RotateModelAboutZ(const GLfloat & angle);
	void RotateCamera(const GLfloat & x, const GLfloat & y, const GLfloat & z);
	void TranslateCamera(const GLfloat & x, const GLfloat & y, const GLfloat & z);
	inline void NextCamera() { activeCamera = (activeCamera + 1) % cameras.size(); }
	inline void PreviousCamera() { activeCamera = (activeCamera + cameras.size()-1) % cameras.size(); }
	inline void ToggleCameraDrawing() { this->m_drawCameras = (!this->m_drawCameras); }
	void ScaleActiveModel(const GLfloat & x, const GLfloat & y, const GLfloat & z);

	void SymScaleActiveModel(const GLfloat & r);

	void UpdateProjectionsForRatio( GLfloat ratio);

	void ToggleFaceNormals();
	void ToggleVertexNormals();
	void toggleBoundingBox();
	void FocusOnActiveModel();
	void addArk();
	void removeActiveModel();
	void removeAllModels();
	inline void SetRenderer( Renderer * r) { m_renderer = r; }
	inline void Zoom( GLfloat rate) { cameras[activeCamera]->Zoom(rate); }
	void MoveZNear(GLfloat len);
	void MoveZFar(GLfloat len);
	inline void SetActivePen(const Pen & p) {m_renderer->SetActivePen(p); }
	inline void SetPassivePen(const Pen & p) { m_renderer->SetPassivePen(p); }
	vec3 SuggestMountingPoint() const;
};